package com.zql.sqlSession;


import com.zql.MappedStatement;
import com.zql.config.BoundSql;
import com.zql.utils.GenericTokenParser;
import com.zql.utils.ParameterMapping;
import com.zql.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {

    public <E> List<E> query(Connection connection, MappedStatement mappedStatement, Object[] param) throws Exception {
        PreparedStatement preparedStatement = this.getPreparedStatement(connection, mappedStatement, param);

        ArrayList<E> results = new ArrayList<E>();

        //5.执行SQL
        ResultSet resultSet = preparedStatement.executeQuery();
        //6.封装返回结果集
        //获取到返回值映射类型/获取到这个对象 也就是 resultType="com.njm.pojo.User"
        Class<?> resultType = mappedStatement.getResultType();
        while (resultSet.next()) {
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            //动态获取类对象
//            E o = (E) resultType.newInstance();
            E o = (E) resultType.getDeclaredConstructor().newInstance();
            //获取列的个数
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                //获取字段名
                String columnName = metaData.getColumnName(i);
                //获取字段值
                Object value = resultSet.getObject(columnName);
                //使用反射/内省，根据数据库表跟实体类的对应关系，完成封装
                //propertyDescriptor构造：给一个属性，再给一个实体对象(bean)，获取bean的这个属性的描述符,就是找到这个属性并(替代/生成)get/set方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
                //获得用于写入属性值的方法 set方法,writeMethod=set
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 写入属性值
                // writeMethod.invoke(o, value) = 类.set属性(value)
                writeMethod.invoke(o, value);
            }
            results.add(o);
        }
        return results;

    }

    private PreparedStatement getPreparedStatement(Connection connection, MappedStatement mappedStatement, Object[] param) throws Exception {
        //2.获取sql语句：select * from user where id = #{id} and username = #{username}
        //转换sql语句：select * from user where id = ？ and username = ？,转换过程中，对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundsql = getBoundSql(sql);

        //3.获取预处理对象：preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundsql.getSqlText());

        //4.设置参数
        //获取到映射参数的全路径/获取到这个对象 也就是 paramterType="com.njm.pojo.User
        Class<?> paramterType = mappedStatement.getParamterType();

        List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            if (paramterType == Integer.class) {
                //第一个是指SQL语句中的第几个参数，第二个是要设置的值，因i是从0开始的,而它的下标是从1开始的所以要i+1
                preparedStatement.setObject(i + 1, param[0]);
            } else {
                //使用反射根据参数名称content，获取实体对象中的属性值，再根据这个属性值获取当前传过来的实体参数里的属性值
                //getDeclaredField：获取类本身的属性成员
                Field declaredField = paramterType.getDeclaredField(content);
                System.out.println(declaredField);
                declaredField.setAccessible(true); //防止属性是私有的，设置为true，意思是在使用时抑制JAVA对象的访问检查。

                //返回指定对象上此 Field 表示的字段的值。拿到当前属性的值
                Object o = declaredField.get(param[0]);
                //第一个是指SQL语句中的第几个参数，第二个是要设置的值，因i是从0开始的,而它的下标是从1开始的所以要i+1
                preparedStatement.setObject(i + 1, o);
            }

        }

        return preparedStatement;
    }

    @Override
    public int CUD(Connection connection, MappedStatement mappedStatement, Object[] param) throws Exception {
        PreparedStatement preparedStatement = this.getPreparedStatement(connection, mappedStatement, param);

        System.out.println(preparedStatement);
        //5.执行SQL
        return preparedStatement.executeUpdate();
    }


    /**
     * 完成对#{}的解析工作：
     * 1.将#{}使用?进行替代。2.解析出#{}里面的值进行存储
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类:配置标记解析器完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }


    public static void main(String[] args) {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        BoundSql boundSql = simpleExecutor.getBoundSql("select * from user where id = #{id}");
    }
}
